﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Markup;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236

namespace BlueRoseGames.Controls.Text
{
    [ContentProperty(Name = "Inlines")]
    public sealed partial class PathTextBlock : UserControl
    {
        GeometryGroup pathGroup;
        Transform transform = null;
        bool textChanged = false;
        DateTime lastUpdateTime = DateTime.MinValue;
        List<Inline> inlines;
        public static readonly DependencyProperty UpdateIntervalSecondsProperty = DependencyProperty.Register("UpdateIntervalSeconds", typeof(double), typeof(PathTextBlock), new PropertyMetadata(null, OnUpdateIntervalSecondsChanged));
        public static readonly DependencyProperty TextTransformProperty = DependencyProperty.Register("TextTransform", typeof(Transform), typeof(PathTextBlock), new PropertyMetadata(null, OnTextTransformChanged));
        public static readonly DependencyProperty FillProperty = DependencyProperty.Register("Fill", typeof(Brush), typeof(PathTextBlock), new PropertyMetadata(new SolidColorBrush(Colors.White), OnFillChanged));
        public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register("StrokeThickness", typeof(double), typeof(PathTextBlock), new PropertyMetadata(1d, OnStrokeThicknessChanged));
        public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register("Stroke", typeof(Brush), typeof(PathTextBlock), new PropertyMetadata(null, OnStrokeChanged));
        public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(PathTextBlock), new PropertyMetadata(null, OnTextChanged));
        TimeSpan updateInterval = TimeSpan.Zero;

        private static void OnTextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var pathTextBlock = (PathTextBlock)dependencyObject;
            pathTextBlock.TextChanged((string)e.OldValue, (string)e.NewValue);
        }

        private static void OnTextTransformChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var pathTextBlock = (PathTextBlock)dependencyObject;
            pathTextBlock.TextTransformChanged((Transform)e.OldValue, (Transform)e.NewValue);
        }

        private static void OnStrokeChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var pathTextBlock = (PathTextBlock)dependencyObject;
            pathTextBlock.StrokeChanged((Brush)e.OldValue, (Brush)e.NewValue);
        }

        private static void OnStrokeThicknessChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var pathTextBlock = (PathTextBlock)dependencyObject;
            pathTextBlock.StrokeThicknessChanged((double)e.OldValue, (double)e.NewValue);
        }

        private static void OnFillChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var pathTextBlock = (PathTextBlock)dependencyObject;
            pathTextBlock.FillChanged((Brush)e.OldValue, (Brush)e.NewValue);
        }

        private static void OnUpdateIntervalSecondsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var pathTextBlock = (PathTextBlock)dependencyObject;
            pathTextBlock.UpdateIntervalSecondsChanged((double)e.OldValue, (double)e.NewValue);
        }

        public void TextChanged(string oldText, string newText)
        {
            textChanged = true;
        }

        public void TextTransformChanged(Transform oldTransform, Transform newTransform)
        {
            transform = newTransform;
        }

        public void StrokeChanged(Brush oldStroke, Brush newStroke)
        {
            textPath.Stroke = newStroke;
        }

        public void StrokeThicknessChanged(double oldStrokeThickness, double newStrokeThickness)
        {
            textPath.StrokeThickness = newStrokeThickness;
        }

        public void FillChanged(Brush oldFill, Brush newFill)
        {
            textPath.Fill = newFill;
        }

        public void UpdateIntervalSecondsChanged(double oldValue, double newValue)
        {
            updateInterval = TimeSpan.FromSeconds(newValue);
        }

        public double UpdateIntervalSeconds
        {
            get
            {
                return (double)GetValue(UpdateIntervalSecondsProperty);
            }
            set
            {
                SetValue(UpdateIntervalSecondsProperty, value);
            }
        }

        public Transform TextTransform
        {
            get
            {
                return (Transform)GetValue(TextTransformProperty);
            }
            set
            {
                SetValue(TextTransformProperty, value);
            }
        }

        public Brush Fill
        {
            get
            {
                return (Brush)GetValue(FillProperty);
            }
            set
            {
                SetValue(FillProperty, value);
            }
        }

        public double StrokeThickness
        {
            get
            {
                return (double)GetValue(StrokeThicknessProperty);
            }
            set
            {
                SetValue(StrokeThicknessProperty, value);
            }
        }

        public Brush Stroke
        {
            get
            {
                return (Brush)GetValue(StrokeProperty);
            }
            set
            {
                SetValue(StrokeProperty, value);
            }
        }

        public string Font
        {
            get;
            set;
        }

        public string Text
        {
            get
            {
                return (string)GetValue(TextProperty);
            }
            set
            {
                SetValue(TextProperty, value);
            }
        }

        Font font = null;
        bool retrievingFont = false;

        private void RenderText()
        {
            if (font == null)
            {
                if (!retrievingFont)
                {
                    retrievingFont = true;
                    FontManager.GetFont(Font).ContinueWith(
                        task =>
                        {
                            font = task.Result;
                        });
                }
                return;
            }
            double scale = FontSize / font.PointSize;
            double offsetX = 0;
            double offsetY = 0;
            Point curPoint = new Point();
            foreach (PathGeometry g in pathGroup.Children)
            {
                foreach (PathFigure fg in g.Figures)
                {
                    foreach (PathSegment s in fg.Segments)
                    {
                        SegmentPool.Instance.AddSegment(s);
                    }
                    fg.Segments.Clear();
                }
                g.Figures.Clear();
            }
            pathGroup.Children.Clear();
            textPath.Data = null;
            pathGroup = new GeometryGroup();
            string renderText = Text;

            RenderFragment(font, scale, ref offsetX, offsetY, ref curPoint, renderText);
            foreach (Inline inline in inlines)
            {
                if (inline is Run)
                {
                    RenderFragment(font, scale, ref offsetX, offsetY, ref curPoint, (inline as Run).Text);
                }
                else if (inline is LineBreak)
                {
                    offsetX = 0;
                    offsetY += font.Baseline * font.PointSize;
                }
            }

            textPath.Data = pathGroup;
            if (transform != null) transform.Changed = false;
            textChanged = false;
        }

        private void RenderFragment(Font f, double scale, ref double offsetX, double offsetY, ref Point curPoint, string renderText)
        {
            if (renderText == null) return;
            foreach (char c in renderText)
            {
                if (f.Glyphs.ContainsKey(c) == false) continue;
                Glyph g = f.Glyphs[c];

                PathGeometry pg = g.PathGeometry;
                curPoint.X = offsetX;
                curPoint.Y = offsetY + f.Baseline * f.PointSize;
                TranslateAndScale(pg, curPoint, scale);
                Transform(pg);
                pathGroup.Children.Add(pg);
                //                Transform(p.Data as PathGeometry);
                offsetX += g.Advance.Width * f.PointSize;
            }
        }

        public Point TranslateAndScalePoint(Point p, Point offset, double scale)
        {
            return new Point((offset.X + p.X) * scale, (offset.Y + p.Y) * scale);
        }

        public void TranslateAndScale(PathGeometry pg, Point offset, double scale)
        {
            foreach (PathFigure fg in pg.Figures)
            {
                fg.StartPoint = TranslateAndScalePoint(fg.StartPoint, offset, scale);
                foreach (PathSegment s in fg.Segments)
                {
                    if (s is LineSegment)
                    {
                        LineSegment ls = s as LineSegment;
                        ls.Point = TranslateAndScalePoint(ls.Point, offset, scale);
                    }
                    else if (s is QuadraticBezierSegment)
                    {
                        QuadraticBezierSegment qs = s as QuadraticBezierSegment;
                        qs.Point1 = TranslateAndScalePoint(qs.Point1, offset, scale);
                        qs.Point2 = TranslateAndScalePoint(qs.Point2, offset, scale);
                    }
                }
            }
        }

        private void Transform(PathGeometry pg)
        {
            if (transform == null) return;
            foreach (PathFigure fg in pg.Figures)
            {
                fg.StartPoint = transform.TransformPoint(fg.StartPoint);
                foreach (PathSegment s in fg.Segments)
                {
                    if (s is LineSegment)
                    {
                        LineSegment ls = s as LineSegment;
                        ls.Point = transform.TransformPoint(ls.Point);
                    }
                    else if (s is QuadraticBezierSegment)
                    {
                        QuadraticBezierSegment qs = s as QuadraticBezierSegment;
                        qs.Point1 = transform.TransformPoint(qs.Point1);
                        qs.Point2 = transform.TransformPoint(qs.Point2);
                    }
                }
            }
        }

        public PathTextBlock()
        {
            InitializeComponent();
            textChanged = false;
            this.Loaded += new RoutedEventHandler(PathTextBlock_Loaded);
            this.LayoutUpdated += PathTextBlock_LayoutUpdated;
            CompositionTarget.Rendering += CompositionTarget_Rendering;
            inlines = new List<Inline>();
        }

        void PathTextBlock_LayoutUpdated(object sender, object e)
        {
            RenderText();

        }

        void CompositionTarget_Rendering(object sender, object e)
        {
            if ((transform != null && transform.Changed) || textChanged)
            {
                if (DateTime.Now - lastUpdateTime > updateInterval)
                {
                    RenderText();
                    lastUpdateTime = DateTime.Now;
                }
            }
            transform.Changed = false;
        }


        void PathTextBlock_Loaded(object sender, RoutedEventArgs e)
        {
            pathGroup = new GeometryGroup();
            textPath.Data = pathGroup;
            RenderText();
        }

        public List<Inline> Inlines
        {
            get
            {
                return inlines;
            }
        }
    }
}
